/* 
 * File:   operator.h
 * Author: gustavo
 *
 * Created on June 10, 2012, 5:39 AM
 */

#ifndef OPERATOR_HPP
#define	OPERATOR_HPP

#include "impl/blaop/mat_primitives.hpp"

namespace imp {

	namespace math {
		template <typename T> class Matrix;

		/* = Matrix Operators = */
		struct Reference;

		struct Conversion;
		struct Assigment;
		struct Increment;
		struct Decrement;
		struct Scale;
		struct Addition;
		struct Subtraction;
		struct Multiplication;

		struct Transpose;
		struct Determinant;
		struct Inverse;
		struct Power;
		struct Negate;

		template<typename T, typename Op> class Operator;

		/* = Increment Operator ================================================= */
		template<typename T> class Operator<T, Increment> {
		private:
			Matrix<T> M;
			T inc;
		public:

			Operator(const Matrix<T>& m, T inc) {
				this->M.refTo(m);
				this->inc = inc;
			}

			void eval(Matrix<T>& MM) const {
				__minc(M.rows, M.columns, M.data, M.stride, inc, MM.data, MM.stride);
			}

			int rows() const {
				return M.rows;
			}

			int columns() const {
				return M.columns;
			}
		};

		/* = Decrement Operator ================================================= */
		template<typename T> class Operator<T, Decrement> {
		private:
			Matrix<T> M;
			T dec;
			bool dec_minus_M;
		public:

			Operator(const Matrix<T>& m, T dec) {
				this->M.refTo(m);
				this->dec = dec;
				this->dec_minus_M = false;
			}

			Operator(T dec, const Matrix<T>& m) {
				this->M.refTo(m);
				this->dec = dec;
				this->dec_minus_M = true;
			}

			void eval(Matrix<T>& MM) const {

				if (dec_minus_M)
					__mdeci(M.rows, M.columns, M.data, M.stride, dec, MM.data, MM.stride);
				else
					__mdec(M.rows, M.columns, M.data, M.stride, dec, MM.data, MM.stride);
			}

			int rows() const {
				return M.rows;
			}

			int columns() const {
				return M.columns;
			}
		};

		/* = Scale Operator ===================================================== */
		template<typename T> class Operator<T, Scale> {
		private:
			Matrix<T> M;
			T s;
		public:

			Operator(const Matrix<T>& m, T s) {
				this->M.refTo(m);
				this->s = s;
			}

			void eval(Matrix<T>& MM) const {
				__mscale(M.rows, M.columns, M.data, M.stride, s, MM.data, MM.stride);
			}

			int rows() const {
				return M.rows;
			}

			int columns() const {
				return M.columns;
			}
		};

		/* = Addition Operator ================================================== */
		template<typename T> class Operator<T, Addition> {
		private:
			Matrix<T> Ml;
			Matrix<T> Mr;

		public:

			Operator(const Matrix<T>& ml, const Matrix<T>& mr) {

				__assert_msg(ml.rows == mr.rows && ml.columns == mr.columns, "Invalid matrix dimensions!");

				this->Ml.refTo(ml);
				this->Mr.refTo(mr);
			}

			~Operator() {
				//__notice("By Operator<T,Addition>!\n");
			}

			void eval(Matrix<T>& MM) const {

				__assert_msg(Ml.rows == MM.rows && Ml.columns == MM.columns, "Invalid matrix dimensions!");

				__madd(Ml.rows, Ml.columns, Ml.data, Ml.stride, Mr.data, Mr.stride, MM.data, MM.stride);
			}

			int rows() const {
				return Ml.rows;
			}

			int columns() const {
				return Ml.columns;
			}
		};

		/* = Subtraction Operator =============================================== */
		template<typename T> class Operator<T, Subtraction> {
		private:
			Matrix<T> Ml;
			Matrix<T> Mr;

		public:

			Operator(const Matrix<T>& ml, const Matrix<T>& mr) {

				__assert_msg(ml.rows == mr.rows && ml.columns == mr.columns, "Invalid matrix dimensions!");

				this->Ml.refTo(ml);
				this->Mr.refTo(mr);

			}

			void eval(Matrix<T>& MM) const {

				__assert_msg(Ml.rows == MM.rows && Ml.columns == MM.columns, "Invalid matrix dimensions!");

				impl::__msub(Ml.rows, Ml.columns, Ml.data, Ml.stride, Mr.data, Mr.stride, MM.data, MM.stride);

			}

			int rows() const {
				return Ml.rows;
			}

			int columns() const {
				return Ml.columns;
			}
		};

		/* = Multiplication Operator ============================================ */
		template<typename T> class Operator<T, Multiplication> {
		private:
			Matrix<T> Ml;
			Matrix<T> Mr;

		public:

			Operator(const Matrix<T>& ml, const Matrix<T>& mr) {

				__assert_msg(ml.columns == mr.rows, "Invalid matrix dimensions!");
				//			if( ml.columns != mr.rows ) {
				//				__assert_msg("Invalid matrix dimensions!\n");
				//			}

				this->Ml.refTo(ml);
				this->Mr.refTo(mr);
			}

			void eval(Matrix<T>& MM) const {
				impl::__mmult(Ml.rows, Ml.columns, Ml.data, Ml.stride, Mr.rows, Mr.columns, Mr.data, Mr.stride, MM.data, MM.stride);
			}

			int rows() const {
				return Ml.rows;
			}

			int columns() const {
				return Mr.columns;
			}
		};


		//	template<typename T> class Operator<T, Reference> {
		//    private:
		//        const Matrix<T>* M;
		//
		//    public:
		//
		//        Operator(const Matrix<T>& m) {
		//            this->M = &m;
		//        }
		//
		//        void eval(Matrix<T>& MM) const {
		//			MM.refTo(*M);
		//        }
		//
		//        int rows() const {
		//            return M->rows;
		//        }
		//
		//        int columns() const {
		//            return M->columns;
		//        }
		//    };
		//
		//    template<typename T> class Operator<T, Conversion> {
		//    private:
		//        const Matrix<T>* M;
		//
		//    public:
		//
		//        Operator(const Matrix<T>& m) {
		//            this->M = &m;
		//        }
		//
		//        template <typename Tout> void eval(Matrix<Tout>& MM) const {
		//            __mcopy_conv(M->rows, M->columns, M->data, M->stride, MM.data, MM.stride);
		//        }
		//
		//        int rows() const {
		//            return M->rows;
		//        }
		//
		//        int columns() const {
		//            return M->columns;
		//        }
		//    };
		//
		//    template<typename T> class Operator<T, Assigment> {
		//    private:
		//        const Matrix<T>* M;
		//
		//    public:
		//
		//        Operator(const Matrix<T>& m) {
		//            this->M = &m;
		//        }
		//
		//        void eval(Matrix<T>& MM) const {
		//            __mcopy(M->rows, M->columns, M->data, M->stride, MM.data, MM.stride);
		//        }
		//
		//        int rows() const {
		//            return M->rows;
		//        }
		//
		//        int columns() const {
		//            return M->columns;
		//        }
		//    };

	}

}
#endif	/* OPERATOR_Hpp */
